/*

  The MIT License

  Copyright (c) 2008 - 2011. Dimarik

  Permission is hereby granted, free of charge, to any person obtaining a copy
  of this software and associated documentation files (the "Software"), to deal
  in the Software without restriction, including without limitation the rights
  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  copies of the Software, and to permit persons to whom the Software is
  furnished to do so, subject to the following conditions:

  The above copyright notice and this permission notice shall be included in
  all copies or substantial portions of the Software.

  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  THE SOFTWARE.

*/

package ru.goodcore.display.ui.core
{

import flash.display.DisplayObject;
import flash.display.DisplayObjectContainer;
import flash.display.Graphics;
import flash.display.Loader;
import flash.display.LoaderInfo;
import flash.display.PixelSnapping;
import flash.display.Stage;
import flash.events.Event;
import flash.events.EventPhase;
import flash.events.IOErrorEvent;
import flash.events.KeyboardEvent;
import flash.events.TimerEvent;
import flash.filters.DropShadowFilter;
import flash.geom.Rectangle;
import flash.text.StyleSheet;
import flash.text.TextField;
import flash.text.TextFieldAutoSize;
import flash.text.TextFormat;
import flash.text.TextFormatAlign;
import flash.ui.Keyboard;
import flash.utils.Timer;
import flash.utils.getQualifiedClassName;

import ru.goodcore.core.GoodCore;
import ru.goodcore.core.ILayoutTracer;
import ru.goodcore.logging.Log;
import ru.goodcore.managers.KeyboardManager;
import ru.goodcore.managers.StyleManager;
import ru.goodcore.text.StyleSheet;
import ru.goodcore.text.StyleTable;
import ru.goodcore.utils.ObjectInfo;


[Style(name="alpha", type="Number", format="Length", inherit="no")]

[Style(name="width", type="Number", format="Length", inherit="no")]

[Style(name="height", type="Number", format="Length", inherit="no")]

[SkinGrid(column="3", row="1", verticalResize="1", horizontalResize="1")]

/**
 *  @author				Dimarik
 *  @version			1.1
 *  @langversion		3.0
 *  @playerversion		9.0
 * 
 */

public class UIComponent extends InvalidableSprite {

	//-------------------------------------------------------------------------
	//
	//   EMBEDS
	//
	//-------------------------------------------------------------------------

	//[Embed(source="../../../../../../../GoodCore_game/src_assets/gfx/brick_blue.png")]
	//private var brickBlue:	Class;

	//-------------------------------------------------------------------------
	//
	//   Class constants
	//
	//-------------------------------------------------------------------------

	/**
	 * @private
	 * Применяется для задержки инвалидации по Stage resize
	 */	
	private static const _stageTimer:	Timer = new Timer(50, 1);

	//-------------------------------------------------------------------------
	//
	//   Constructor
	//
	//-------------------------------------------------------------------------

	/**
	 * Constructor 
	 */
	public function UIComponent() {
		super();
		super.visible    = false;
		super.tabEnabled = false;
		super.focusRect  = false;
		this._objectInfo = ObjectInfo.getInfo(this);

		// Внутреннее поведение используется только для служебных целей и только видно в подклассах.
		// Пока не заданы пропорциональные и точные размеры,
		// инициализируем внутреннее поведение как зависящее от контента (детей).
		// Внетренне поведение может быть трех видов: EXPLICIT, CHILD, PARENT.
		this._widthDepentInternal = SizeDependet.CHILD;
		this._heightDepentInternal = SizeDependet.CHILD;

		// Внешнее поведение видно для экземпляров родителей.
		// Поведение их ребенка всегда либо точное, либо пропорциональное.
		// Внешнее поведение может быть только двух видов: EXPLICIT или PARENT.
		this._widthDepent = SizeDependet.EXPLICIT;
		this._heightDepent = SizeDependet.EXPLICIT;

		this._pixelSnappingFunction = this.pixelSnappingNever;

		super.addEventListener(Event.ADDED_TO_STAGE,		this.handler_addedToStage,		false, int.MAX_VALUE);
		super.addEventListener(Event.REMOVED_FROM_STAGE,	this.handler_removedFromStage,	false, int.MAX_VALUE);
		super.addEventListener(GoodCoreEvent.INVALIDATE,	this.handler_invalidate,		false, int.MAX_VALUE);

		this._styleSheet.addEventListener(Event.CHANGE,		this.handler_styleSheetChanged, false, int.MAX_VALUE);

		this._styleTableOwn.addEventListener(Event.CHANGE,	this.handler_styleTableOwnChange);
		this._styleTableOwn.addEventListener(Event.CLEAR,	this.handler_styleTableOwnClear);

		var styleTable:StyleTable = this._styleManager.getStyleTable(this);
	}

	//-------------------------------------------------------------------------
	//
	//   Overridden properties:	DisplayObject
	//
	//-------------------------------------------------------------------------

	//=====================================================
	//   Explicite Size 
	//=====================================================

	//---------------------------------
	//  width
	//---------------------------------

	/**
	 * @private 
	 * 
	 * Здесь запоминается "сырая" ширина, выставленная объекту через свойство width.
	 * Актуальная ширина всегда предварительно рассчитывается, чтобы лишний раз 
	 * не дергать инвалидацию.
	 */
	private var _width:			Number = NaN;

	/**
	 * @private 
	 */
	private var _widthChanged:	Boolean = false;

	[PercentProxy("percentWidth")]
	/**
	 * @param value
	 * Устанавливается точный размер.
	 */
	public override function set width(value:Number):void {
		if ( this._width === value )
			return;

		this._percentWidth = NaN;
		this._percentWidthChanged = false;

		if ( isNaN(value) ) { // Перешли на зависимость от детей.
			this._width = value;
			this._widthDepent = SizeDependet.CHILD;
			this._widthChanged = true;
			this.invalidate();
		}

		else  {

			// сырая ширина была неопределенной, значит переходим на точную зависимость
			if ( isNaN(this._width) ) { 
				this._widthDepent = 
				this._widthDepentInternal = SizeDependet.EXPLICIT;
			}

			this._width = value;
			this.$width = value;
		}

	}

	public function set $width(value:Number):void {
		value = this._pixelSnappingFunction(value);
		
		const old:	Number = ui_internal::$valideWidth;

		// Сначала расставим детей
		ui_internal::valideWidth = value;

		// Произошел перерасчет
		if ( ui_internal::$valideWidth !== old	) {
			this._widthChanged = true;
			this.invalidate();
		}
	}

	public override function get width():Number {
		return ui_internal::$valideWidth;
	}

	//---------------------------------
	//  height
	//---------------------------------

	/**
	 * @private 
	 * 
	 * Здесь запоминается "сырая" длина, выставленная объекту через свойство height.
	 * Актуальная длина всегда предварительно рассчитывается, чтобы лишний раз 
	 * не дергать инвалидацию.
	 */
	private var _height:		Number = NaN;

	/**
	 * @private 
	 */
	private var _heightChanged:	Boolean = false;

	[PercentProxy("percentHeight")]
	/**
	 * @param value
	 * Устанавливается точный размер.
	 */
	public override function set height(value:Number):void {
		if ( this._height === value )
			return;

		this._percentHeight = NaN;
		this._percentHeightChanged = false;

		if ( isNaN(value) ) { // Перешли на зависимость от детей.
			this._height = value;
			this._heightDepent = SizeDependet.CHILD;
			this._heightChanged = true;
			this.invalidate();
		}

		else  {

			// сырая длина была неопределенной, значит переходим на точную зависимость
			if ( isNaN(this._height) ) { 
				this._heightDepent = 
				this._heightDepentInternal = SizeDependet.EXPLICIT;
			}

			this._height = value;
			this.$height = value;
		}

	}

	public override function get height():Number {
		return ui_internal::$valideHeight;
	}

	public function set $height(value:Number):void {
		value = this._pixelSnappingFunction(value);

		const old:	Number = ui_internal::$valideHeight;

		// Сначала расставим детей
		ui_internal::valideHeight = value;

		// Произошел перерасчет
		if ( ui_internal::$valideHeight !== old	) {
			this._heightChanged = true;
			this.invalidate();
		}
	}

	//---------------------------------
	//  x
	//---------------------------------

	public override function set x(value:Number):void {
		super.x = this._pixelSnappingFunction(value);
	}

	//---------------------------------
	//  y
	//---------------------------------

	public override function set y(value:Number):void {
		super.y = this._pixelSnappingFunction(value);
	}

	//---------------------------------
	//  numChildren
	//---------------------------------
	/**
	 * @inheritDoc
	 */
	public override function get numChildren():int {
		return this._internalContainer ? super.numChildren - 1 : super.numChildren;
	}

	//-------------------------------------------------------------------------
	//
	//   Properties
	//
	//-------------------------------------------------------------------------

	//---------------------------------
	//  valideWidth
	//---------------------------------

	ui_internal var $valideWidth:			Number = 0;

	ui_internal function set valideWidth(value:Number):void {
		if ( ui_internal::$valideWidth === value )
			return;

		var rounded:	Number = this._pixelSnapping === PixelSnapping.ALWAYS ?
				Math.round(value) : // До целого
				Math.round(value * 10) / 10; // До 0.1 пикселя достаточно

		if ( ui_internal::$valideWidth === rounded ) 
			return;

		ui_internal::$valideWidth = rounded;

		// По идее сейчас тот момент, когда валидация должна идти от корня к кроне.
		// Мы поменяли свой размер и должны заново сделать раскладку детей.
		this.layoutTracer.layoutObjects();
		this._sizeChanged = true;
		super.invalidate(); // super!
	}

	ui_internal function get valideWidth():Number {
		return ui_internal::$valideWidth;
	}


	//---------------------------------
	//  valideWidth
	//---------------------------------

	ui_internal var $valideHeight:			Number = 0;

	ui_internal function set valideHeight(value:Number):void {
		if ( ui_internal::$valideHeight === value )
			return;

		var rounded:	Number = this._pixelSnapping === PixelSnapping.ALWAYS ?
				Math.round(value) : // До целого
				Math.round(value * 10) / 10; // До 0.1 пикселя достаточно

		if ( ui_internal::$valideHeight === rounded ) 
			return;

		ui_internal::$valideHeight = rounded;

		// По идее сейчас тот момент, когда валидация должна идти от корня к кроне.
		// Мы поменяли свой размер и должны заново сделать раскладку детей.
		this.layoutTracer.layoutObjects();
		this._sizeChanged = true;
		super.invalidate(); // super!
	}

	ui_internal function get valideHeight():Number {
		return ui_internal::$valideHeight;
	}

	//---------------------------------
	//  widthDepent
	//---------------------------------
	/**
	 * @private
	 */
	private var _widthDepent:	SizeDependet;

	/**
	 * @private
	 */
	private var _widthDepentInternal:	SizeDependet;
/*
	public function get widthDepent():SizeDependet {
		return this._widthDepent;
	}

	public function set widthDepent(value:SizeDependet):void {
		this._widthDepent = value;
	}
*/
	//---------------------------------
	//  heightDepent
	//---------------------------------
	/**
	 * @private
	 */
	private var _heightDepent:			SizeDependet;

	/**
	 * @private
	 */
	private var _heightDepentInternal:	SizeDependet;
/*
	public function get heightDepent():SizeDependet {
		return this._heightDepent;
	}

	public function set heightDepent(value:SizeDependet):void {
		this._heightDepent = value;
	}
*/
	//---------------------------------
	//  percentWidth
	//---------------------------------

	/**
	 * @private 
	 */
	private var _percentWidth:			Number = NaN;

	/**
	 * @private 
	 */
	private var _percentWidthChanged:	Boolean = false;

	/** 
	 * @param value
	 * Устанавливается пропорциональный размер.
	 */
	public function set percentWidth(value:Number):void {
		if ( this._percentWidth === value )
			return;

		if ( isNaN(value) ) {
			this._percentWidth = NaN;
			this._widthDepentInternal = SizeDependet.CHILD;
			this._percentWidthChanged = true;
			this._widthChanged = false;
		} else {
			this._percentWidth = value;
			this._width = NaN;
			this._widthDepent = 
			this._widthDepentInternal = SizeDependet.PARENT;
			this._percentWidthChanged = true;
			this._widthChanged = false;
		}

		this.invalidate();
	}

	public function get percentWidth():Number {
		return this._percentWidth;
	}


	//---------------------------------
	//  percentHeight
	//---------------------------------
	/**
	 * @private 
	 */
	private var _percentHeight:			Number = NaN;

	/**
	 * @private 
	 */
	private var _percentHeightChanged:	Boolean = false;

	/** 
	 * @param value
	 * Устанавливается пропорциональный размер.
	 */
	public function set percentHeight(value:Number):void {
		if ( this._percentHeight === value )
			return;

		if ( isNaN(value) ) {
			this._percentHeight = NaN;
			this._heightDepentInternal = SizeDependet.CHILD;
			this._percentHeightChanged = true;
			this._heightChanged = false;
		} else {			
			this._height = NaN;
			this._heightDepent = 
			this._heightDepentInternal = SizeDependet.PARENT;
			this._percentHeight = value;
			this._percentHeightChanged = true;
			this._heightChanged = false;
		}

		this.invalidate();
	}

	public function get percentHeight():Number {
		return this._percentHeight;
	}

	//-------------------------------------------------------------------------
	//
	//   Properties
	//
	//-------------------------------------------------------------------------

	//---------------------------------
	//  pixelSnapping
	//---------------------------------

	/**
	 * @private
	 */
	private var _pixelSnapping:			String = PixelSnapping.NEVER;

	/**
	 * @private 
	 */
	private var _pixelSnappingFunction:	Function;

	public function get pixelSnapping():String {
		return this._pixelSnapping;
	}

	public function set pixelSnapping(value:String):void {
		switch (value) {
			case PixelSnapping.NEVER:
			case PixelSnapping.ALWAYS:
				break;
			default:
				return;
		}

		if (this._pixelSnapping === value) 
			return;

		this._pixelSnapping = value;

		this._pixelSnappingFunction = value == PixelSnapping.ALWAYS ?
			this.pixelSnappingAlways :
			this.pixelSnappingNever;

		this.$width = this._width; 
		this.$height = this._height;
	}

	private function pixelSnappingAlways(value:Number):Number {
		return Math.round(value); // До целого
	}

	private function pixelSnappingNever(value:Number):Number {
		return Math.round(value * 10) / 10; // До 0.1 пикселя достаточно
	}

	//-------------------------------------------------------------------------
	//
	//   Properties
	//
	//-------------------------------------------------------------------------

	//---------------------------------
	//  initialized
	//---------------------------------
	/**
	 * @private
	 */
	private var _initialized:	Boolean = false;

	public function get initialized():Boolean {
		return this._initialized;
	}

	//---------------------------------
	//  parent
	//---------------------------------
	/**
	 * @private
	 */
	private var _parent:	DisplayObjectContainerDecorator;

	protected function get $parent():DisplayObjectContainerDecorator {
		if ( !this._parent ) {

			var parent:DisplayObjectContainer = super.parent;

			if (parent is Loader)
				parent = parent.parent || parent;

			if (parent)
				this._parent = new DisplayObjectContainerDecorator(parent);
		}

		return this._parent;
	}

	//---------------------------------
	//  includeInLayout
	//---------------------------------
	/**
	 * @private
	 */
	private var _includeInLayout:	Boolean = true;

	public function get includeInLayout():Boolean {
		return this._includeInLayout;
	}

	public function set includeInLayout(value:Boolean):void {
		if (this._includeInLayout === value)
			return;
		this.invalidate();
	}

	//---------------------------------
	//  color
	//---------------------------------
	/**
	 * @private
	 */
	private var _color:			uint = 0x034569;

	/**
	 * @private
	 */
	private var _colorChanged:	Boolean = false;

	public function set color(value:uint):void {
		this._color = value;
		this._colorChanged = true;
		this.invalidate();
	}

	public function get color():uint {
		return this._color;
	}

	//---------------------------------
	//  clipContent
	//---------------------------------
	/**
	 * @private
	 */
	private var _clipContent:			Boolean = false;

	/**
	 * @private
	 */
	private var _clipContentChanged:	Boolean = false;

	public function set clipContent(value:Boolean):void {
		if (this._clipContent === value) return;
		this._clipContent = value;
		this._clipContentChanged = true;
		super.invalidate();
	}

	public function get clipContent():Boolean {
		return this._clipContent;
	}

	//---------------------------------
	//  objectInfo
	//---------------------------------
	/**
	 * @private
	 */
	private var _objectInfo:			ObjectInfo;

	public function get objectInfo():ObjectInfo {
		return this._objectInfo;
	}













	/**
	 * @private 
	 */	
	protected var styleChanged:			Boolean = false;

	//---------------------------------
	//  styleManager
	//---------------------------------

	private var _styleManager:	StyleManager = StyleManager.instance;

	public function get styleManager():StyleManager {
		if ( !this._styleManager )
			this._styleManager = StyleManager.instance;

		return _styleManager;
	}

	//---------------------------------
	//  style
	//---------------------------------
	/**
	 * @private
	 */
	private var _style:		Object = new Object();

	public function get style():Object {
		return this._style;
	}

	//---------------------------------
	//  styleName
	//---------------------------------
	/**
	 * @private
	 */
	private var _styleName:		String;

	public function set styleName(value:String):void {
		if ( this._styleName == value )
			return;
		this._styleName = value;
		this.updateStyles();
	}

	public function get styleName():String {
		return this._styleName;
	}

	//---------------------------------
	//  styleSheet
	//---------------------------------
	/**
	 * @private
	 */
	private const _styleSheet:		ru.goodcore.text.StyleSheet = new ru.goodcore.text.StyleSheet();

	/**
	 * @private
	 */
	private var _styleSheetChanged:	Boolean;

	public function set styleSheet(value:flash.text.StyleSheet):void {
		this._styleSheet.mergeStyleSheet(value);
	}

	public function get styleSheet():flash.text.StyleSheet {
		return this._styleSheet;
	}


	//---------------------------------
	//  styleTableOwn
	//---------------------------------
	/**
	 * @private
	 */
	private var _styleTableOwn: StyleTable = new StyleTable();

	public function set styleTableOwn(value:StyleTable):void {
		if (this._styleTableOwn === value)
			return;

		if (this._styleTableOwn) {
			this._styleTableOwn.removeEventListener(Event.CHANGE,	this.handler_styleTableOwnChange);
			this._styleTableOwn.removeEventListener(Event.CLEAR,	this.handler_styleTableOwnClear);
		}

		this._styleTableOwn = value;

		if (value) {
			this._styleTableOwn.addEventListener(Event.CHANGE,	this.handler_styleTableOwnChange);
			this._styleTableOwn.addEventListener(Event.CLEAR,	this.handler_styleTableOwnClear);
			this.updateStyles();
		}

	}

	public function get styleTableOwn():StyleTable {
		return this._styleTableOwn;
	}

	private function handler_styleTableOwnChange(event:Event):void {
		//trace('handler_styleTableOwnChange', event);
	}

	private function handler_styleTableOwnClear(event:Event):void {
		//trace('handler_styleTableOwnClear', event);
	}










	//-------------------------------------------------------------------------
	//
	//   Properties
	//
	//-------------------------------------------------------------------------

	//---------------------------------
	//  keyboardManager
	//---------------------------------
	/**
	 * @private
	 */
	private var _keyboardManager:	KeyboardManager;

	public function get keyboardManager():KeyboardManager {
		if ( !this._keyboardManager )
			this._keyboardManager = GoodCore.keyboardManager;

		return this._keyboardManager;
	}

	//---------------------------------
	//   layoutTracer
	//---------------------------------
	/**
	 * @private 
	 */	
	private var _layoutTracer:	ILayoutTracer;

	public function set layoutTracer(value:ILayoutTracer):void {
		if (this._layoutTracer === value)
			return;

		this._layoutTracer = value;
		value.target = this;
		this.invalidate();
	}

	public function get layoutTracer():ILayoutTracer {
		if ( !this._layoutTracer ) {
			this._layoutTracer = new DefaultTracer();
			this._layoutTracer.target = this;
		}
		return this._layoutTracer;
	}

	//-------------------------------------------------------------------------
	//
	//   Variables & const
	//
	//-------------------------------------------------------------------------

	/**
	 * @private
	 */
	private var _stageListening:		Boolean = false;

	/**
	 * @private
	 */
	private var _boundTextField:		TextField;

	/**
	 * @private
	 */
	private var _internalContainer:		DisplayObjectContainer;

	/**
	 * @private
	 */
	private const _list:				Vector.<DisplayObject> = new Vector.<DisplayObject>();

	/**
	 * @private
	 */
	private var _added:					Boolean = false;

	/**
	 * @private
	 */
	private var _childAdded:			Boolean = false;

	/**
	 * @private 
	 */
	private var _sizeChanged:			Boolean = true;

	/**
	 * @private 
	 */
	private var _showBounding:			Boolean = true;

	/**
	 * @private 
	 */
	private var _showBoundingChanged:	Boolean = false;

	//-------------------------------------------------------------------------
	//
	//   Overridden methods: DisplayObjectContainer
	//
	//-------------------------------------------------------------------------

	/**
	 * @inheritDoc 
	 */
	public override function addChild(child:DisplayObject):DisplayObject {
		if ( child ) {

			const list:		Vector.<DisplayObject> = this._list;
			const index:	int = list.indexOf(child);

			// Кеширование
			if ( index === -1 )
				list.push(child);
			else {
				list.splice( index, 1 );
				list.splice( index, 0, child ); // Смена индекса
			}

			if ( child is UIComponent ) {

				super.addChild(child);

			} else {

				if ( child is Loader ) {
					const loader:	Loader = child as Loader;
					const li:		LoaderInfo = loader.contentLoaderInfo;

					// На случай, если этот лоадер будут использовать повторно.
					// Если уже был подписан. то ничего страшного не произойдет.
					li.addEventListener(Event.COMPLETE,			this.handler_loader);
					li.addEventListener(IOErrorEvent.IO_ERROR,	this.handler_loader);

					if (li.contentType !== null) {
						// Уже загрузился
						
						//this.invalidate();
					}

				}

				super.addChild(child);
				this._childAdded = true;
				this.invalidate();
			}

			return child;

		} else {

			return super.addChild(child);

		}

	}

	/**
	 * @private
	 */
	private function handler_loader(event:Event):void {
		this.invalidate();
	}

	/**
	 * @inheritDoc 
	 */
	public override function removeChild(child:DisplayObject):DisplayObject {

		if (child) {

			const index:int = this._list.indexOf(child);

			if ( index !== -1 )
				this._list.splice(index, 1);

			if ( child is Loader ) {
				const loader:	Loader = child as Loader;
				const li:		LoaderInfo = loader.contentLoaderInfo;
				li.removeEventListener(Event.COMPLETE,			this.handler_loader);
				li.removeEventListener(IOErrorEvent.IO_ERROR,	this.handler_loader);
			}
		}

		return super.removeChild(child);
	}

	/**
	 * @inheritDoc 
	 */
	public override function getChildAt(index:int):DisplayObject {
		if ( this._internalContainer ) {
			return super.getChildAt(index + 1);
		}
		return super.getChildAt(index);
	}

	//-------------------------------------------------------------------------
	//
	//   Public methods: DisplayList operation
	//
	//-------------------------------------------------------------------------

	/**
	 * Возвращает доступных детей
	 * @return Vector.<DisplayObject>
	 */
	public function getChildren():Vector.<DisplayObject> {
		return this._list.slice();
	}


	//-------------------------------------------------------------------------
	//
	//   Overridden methods: InvalidableSprite
	//
	//-------------------------------------------------------------------------
	/**
	 * 
	 */
	public override function invalidate():void {
		if ( 
			   !this._initialized
			|| !this.$parent
		) {
			return;
		}

		super.invalidate();

		// Событие всплывающее.
		// Изменение собственных размеров может:
		// - повлиять на размеры родительского контейнера;
		// - спровоцировать перерасчет раскладки.
		super.dispatchEvent( new GoodCoreEvent(GoodCoreEvent.INVALIDATE, true) );
	}

	/**
	 * @private
	 */
	public override function validate():void {
		super.validate();

		CONFIG::debug {

			if (
				   this._sizeChanged
				|| this._showBoundingChanged
				|| this._colorChanged
			) {
				const g:Graphics = super.graphics;
				g.clear();

				const width:	Number = ui_internal::$valideWidth;
				const height:	Number = ui_internal::$valideHeight;

				if ( this._showBounding ) {

					if ( width && height ) {
						//const bitmap:Bitmap = new brickBlue() as Bitmap;
						//g.lineStyle(1, 0x64AAD0, 1, true);
						g.beginFill(this._color, 0.3);
						//g.beginBitmapFill(bitmap.bitmapData, null, true);
						//g.drawRect(0, 0, width - 1, height - 1);
						g.drawRect(0, 0, width, height);
						g.endFill();

						var textField:	TextField = this._boundTextField;
						if ( !textField ) {
							textField = this._boundTextField = new TextField();
							textField.mouseEnabled = 
							textField.selectable = false;
							textField.autoSize = TextFieldAutoSize.CENTER;
							textField.alpha = 1;
							const tf:TextFormat = textField.defaultTextFormat;
							tf.font = '_sans';
							tf.size = 9;
							tf.color = 0xFFFFFF;
							tf.align = TextFormatAlign.LEFT;
							textField.defaultTextFormat = tf;
							textField.filters = [
								new DropShadowFilter(2, 45, 0x000000, 1, 2, 2, 2, 1)
							];
						}

						textField.text = 
							width  + this._widthDepentInternal.toString() +
							' x ' + 
							height + this._heightDepentInternal.toString() +
							' ' + this._pixelSnapping.toString() + 
							'\n' + 
							this._percentWidth +
							'% x ' + 
							this._percentHeight + '%';

						textField.x = (width - textField.width) >> 1;
						textField.y = (height - textField.height) >> 1;

						if ( !textField.parent ) {

							if ( !this._internalContainer ) {
								this._internalContainer = new InternalContainer();
								super.addChildAt(this._internalContainer, 0);
							}

							this._internalContainer.addChild(textField);
						}

					}
				} else {
					if (this._boundTextField && this._boundTextField.parent)
						this._boundTextField.parent.removeChild(this._boundTextField);
				}

				this._showBoundingChanged = false;
				this._colorChanged = false;
			}

			if ( 
				   this._sizeChanged
				|| this._clipContentChanged 
			) {

				this._clipContentChanged = false;

				if ( this._clipContent ) {

					const rect:Rectangle = super.scrollRect;

					if ( rect ) {
						rect.width = width;
						rect.height = height;
						super.scrollRect = rect;
					} else {
						super.scrollRect = new Rectangle(0, 0, width, height);
					}

				} else {
					super.scrollRect = null;
				}
			}
			
			if ( this._styleSheetChanged ) {
				// Размеры
			}

		}

		this._sizeChanged = false;

	}

	/**
	 * @private
	 */
	private function updateStyles():void {
		//[Style(name="alpha", type="Number", format="Length", inherit="no")]
		//[Style(name="paddingRight", type="Number", format="Number", inherit="no")]
		//[Style(name="horizontalAlign", type="String", enumeration="left,center,right", inherit="no")]

		var objectInfo:	ObjectInfo;
		var target:		Object;

		var metaSelf:	XMLList = this._objectInfo.getMetadata('Style');

		if (this.layoutTracer && this.layoutTracer.objectInfo)
			var metaTracer:XMLList = this.layoutTracer.objectInfo.getMetadata('Style');

		if ( !metaSelf.length() && !metaTracer.length() )
			return;

		var style:	Object = this.styleManager.getStyleByTypeSelector(this._objectInfo);

		// TODO 1. «global» selector

		// TODO 2. Ancestor class's Type selectors

		// TODO 3. Parent chain (inheritable styles only)

		// 4. Type selector
		for (var styleName:String in style)
			this._style[styleName] = style[styleName];

		// 5. Class selector
		if ( this._styleName ) {
			style = this.styleManager.getStyleByClassSelector(this._styleName);

			for (styleName in style)
				this._style[styleName] = style[styleName];
		}

		// Все возможные стили присутствуют в this._style

		for (styleName in this._style) {

			var propDescription:XML = (metaSelf.arg.(@value==styleName) as XMLList).parent();
			objectInfo = this._objectInfo;
			target = this;

			if (
				   ( !propDescription || !propDescription.length() )
				&& metaTracer 
				&& metaTracer.length() 
			) {
				propDescription = (metaTracer.arg.(@value==styleName) as XMLList).parent();
				objectInfo = this.layoutTracer.objectInfo;
				target = this.layoutTracer;
			}

			if (!propDescription)
				continue;

			var value:		String = this._style[styleName];
			var type:		String = propDescription.arg.(@key=='type').@value.toString();
			var format:		String = propDescription.arg.(@key=='format').@value.toString().toLocaleLowerCase();
			var selpProp:	ObjectInfo = objectInfo.getMember(styleName);

			if ( format == 'length' ) {

				if ( value.match(/\%/) ) {

					var xml:XMLList = selpProp.getMetadata('PercentProxy');
					if (xml.length()) {
						var nValue:Number = parseFloat( value.split('%')[0] );
						var percentProxyName:String = xml.arg.@value.toString()
						var percentProxyProp:ObjectInfo =  objectInfo.getMember(percentProxyName);

						target[percentProxyProp.localName] = nValue;
					}

				} else {
					nValue = parseFloat(value);
					target[selpProp.localName] = nValue;
				}

			} else if ( format == 'enum' ) {
				var enumeration:Array = propDescription.arg.(@key=='enumeration').@value.toString().toLocaleLowerCase().split(',');
				var iValue:int = enumeration.indexOf(value);
				if (iValue !== -1)
					target[selpProp.localName] = iValue;
			}

		}

	}

	//-------------------------------------------------------------------------
	//
	//   Protected methods
	//
	//-------------------------------------------------------------------------

	/**
	 *  
	 */
	protected function initialize():void {
		if ( this._initialized )
			return;

		if ( super.hasEventListener(Event.OPEN) )
			super.dispatchEvent( new Event(Event.OPEN) ); // preinitialize event

		this.createChildren();

		this.updateStyles();

		this._initialized = true;
		super.visible = true;

		if ( super.hasEventListener(Event.INIT) )
			super.dispatchEvent( new Event(Event.INIT) ); // initialized event
	}

	/**
	 *  
	 */
	protected function createChildren():void {
		//super.invalidate();
	}

	//-------------------------------------------------------------------------
	//
	//   Public methods
	//
	//-------------------------------------------------------------------------

	public function setSize(widht:Number, height:Number):void {
		this.width = widht;
		this.height = height;
	}

	//-------------------------------------------------------------------------
	//
	//   Private methods
	//
	//-------------------------------------------------------------------------

	/**
	 * @private
	 */
	private function assertStageListening():void {
		if (
			   this._widthDepent == SizeDependet.PARENT
			|| this._heightDepent == SizeDependet.PARENT
		) {
			if ( !this._stageListening ) {
				this._stageListening = true;
				this.addStageListenrsBeing(super.stage.addEventListener, _stageTimer.addEventListener);
			}
		}

		// Поведение не зависит от родителя (базовое поведение EXPLICIT).
		// Нам не нужно больше слушать надстройку над RESIZE 
		else {
			if ( this._stageListening ) {
				this._stageListening = false;
				this.addStageListenrsBeing(super.stage.removeEventListener, _stageTimer.removeEventListener);
			}
		}
	}


	/**
	 * @private
	 */
	private function addStageListenrsBeing(methodStage:Function, methodTimer:Function):void {
		methodStage.call(null, Event.RESIZE,				this.handler_stageResized);
		methodTimer.call(null, TimerEvent.TIMER_COMPLETE,	this.handler_stageTimerComplete);
	}

	//-------------------------------------------------------------------------
	//
	//   Event handlers
	//
	//-------------------------------------------------------------------------

	/**
	 * @private
	 */
	protected function handler_invalidate(event:Event):void {

		// Этот обработчик вызывается при распространении события INVALIDATE
		// Источником события может быть сам компонент.
		CONFIG::logging { Log.getLogger(UIComponent).info('handler_invalidate ' + super.name); }

		var dispatch:	Boolean = true;
		var layouted:	Boolean = false;

		// Валидация вызвана добавлением текущего объекта в список отображения
		if ( this._added ) {

			this._added = false;
			this._widthChanged = false;
			this._heightChanged = false;
			this._percentWidthChanged = false;
			this._percentHeightChanged = false;

			var parent:		DisplayObject = super.parent;

			if ( parent is Loader ) {
				// По идее здесь всегда будет ссылка на parent.
				// Цепочка слушателей здесь самая короткая и некому разлучить 
				// лоадер с родителем.
				parent = parent.parent;
			}

			if ( parent is Stage ) {
				// Попробуем слушать Stage RESIZE.
				this.assertStageListening();
				dispatch = false;
			}

			else if ( !(parent is UIComponent) ) {
				// Родитель не UIC. Нет смысла бабблить INVALIDATE.
				// Только нужно будет с его размерами сопоставиться, если требуется.
				dispatch = false;
			}

			if ( !dispatch ) {
				// Нет нужды баблить INVALIDATE. Наверху нам никто не поможет.
				// Будем разбираться с размерами самостоятельно.

				// Смотрим на своё внутреннее и базовое поведение и решаем, нужно ли менять нам
				// наши размеры и раскладывать детей по-новой.

				if ( this._widthDepentInternal === SizeDependet.PARENT ) { // Ширина зависит от родителя
					ui_internal::valideWidth = this._percentWidth * this.$parent.width / 100;
					layouted = true;
				}

				if ( this._heightDepentInternal === SizeDependet.PARENT ) {
					ui_internal::valideHeight = this._percentHeight * this.$parent.height / 100;
					layouted = true;
				}

			}

		}

		// В текущий контейнер добавлены дети
		if ( this._childAdded && !layouted ) {
			this._childAdded = false;
			dispatch = true;
		}

		// Изменил(ся/ись) наши точные размеры
		if (
			   this._widthChanged
			|| this._heightChanged
		) {
			this._widthChanged = false;
			this._heightChanged = false;
			// Изменилась наша точная ширина и(или) высота.
			// Необходимо сделать раскладку детей и оповестить родителя.
			this.layoutTracer.layoutObjects();
			layouted = true;
		}

		// Изменился наш относительный размер по ширине
		if ( this._percentWidthChanged ) {

			this._percentWidthChanged = false;

			if (this.$parent.aggregate is UIComponent) {
				// Родитель UIC, пусть сам выставит мне ширину после всплытия INVALIDATE.
			} else {
				// Родитель не UIC, уже можно определить свою ширину.
				ui_internal::valideWidth = int(this.$parent.width * this._percentWidth / 100);
				dispatch = false;
				layouted = true;
			}
		}

		// Изменился наш относительный размер по высоте
		if ( this._percentHeightChanged ) {

			this._percentHeightChanged = false;

			if (this.$parent.aggregate is UIComponent) {
				// Родитель UIC, пусть сам выставит мне высоту после всплытия INVALIDATE.
			} else {
				// Родитель не UIC, можно определить свою высоту.
				ui_internal::valideHeight = int(this.$parent.height * this._percentHeight / 100);
				dispatch = false;
				layouted = true;
			}
		}


		if (dispatch) {

			if (
				   this._widthDepentInternal === SizeDependet.CHILD
				|| this._heightDepentInternal === SizeDependet.CHILD
			) {
				var child:		DisplayObject;
				var uiChild:	UIComponent;

				// Изменение размеров ребенка может повлиять на нас, если мы зависим от детей.
				// Предварительно определим, будет бабблинг или нет.
				// Если мы имеем точную ширину, то изменения ребенка приведут к изменению нашей ширины.
				// Значит, баблинг необходим. 
				// Если мы уже зависим от PARENT, то баблинг пойдет, если ВСЕ дети будут EXPLICIT.
				dispatch = this._widthDepent === SizeDependet.EXPLICIT || this._heightDepent === SizeDependet.EXPLICIT;

				// Проверим всех детей на поведение

				var numChildren:				uint = this.numChildren;
				var myChildsWidthDepentOnMe:	Boolean = false;
				var myChildsHeightDepentOnMe:	Boolean = false;

				while (numChildren--) {

					child = this.getChildAt(numChildren);

					uiChild = child as UIComponent;

					if ( uiChild ) {
						// Если ребеночек зависит от родителя
						if ( uiChild._widthDepent === SizeDependet.PARENT ) {
							myChildsWidthDepentOnMe = true;
							// Если кто-то из детей уже был зависим от меня по высоте, то можно не продолжать.
							if (myChildsHeightDepentOnMe)
								break;
						}

						if ( uiChild._heightDepent === SizeDependet.PARENT ) {
							myChildsHeightDepentOnMe = true;
							// Если кто-то из детей уже был зависим от меня по ширине, то можно не продолжать.
							if (myChildsWidthDepentOnMe)
								break;
						}
					}
				}

				if ( myChildsWidthDepentOnMe ) {
					if ( this._widthDepent === SizeDependet.EXPLICIT ) {
						// Значит, какой-то ребенок поменял свою зависимость ширины на PARENT
						this._widthDepent = SizeDependet.PARENT;
						this._percentWidth = 100;
						dispatch = true;
					}
				} else {
					if ( this._widthDepent === SizeDependet.PARENT ) {
						// Значит, какой-то этот ребенок поменял свою зависимость ширины на EXPLICIT
						this._widthDepent = SizeDependet.EXPLICIT;
						this._percentWidth = NaN;
						dispatch = true;
					}
				}

				if ( myChildsHeightDepentOnMe ) {
					if ( this._heightDepent === SizeDependet.EXPLICIT ) {
						// Значит, этот ребенок поменял свою зависимость высоты на PARENT
						this._heightDepent = SizeDependet.PARENT;
						this._percentHeight = 100;
						dispatch = true;
					}
				} else {
					if ( this._heightDepent === SizeDependet.PARENT ) {
						// Значит, этот ребенок поменял свою зависимость высоты на EXPLICIT
						this._heightDepent = SizeDependet.EXPLICIT;
						this._percentHeight = NaN;
						dispatch = true;
					}
				}

				// Нужно понять, поменялось ли мое базовое поведение на EXPLICIT
				if (
					   this._widthDepent === SizeDependet.EXPLICIT
					|| this._heightDepent === SizeDependet.EXPLICIT
				) {

					const rect:Rectangle = this.layoutTracer.measure();

					if ( this._widthDepent === SizeDependet.EXPLICIT )
						ui_internal::valideWidth = rect.width;

					if ( this._heightDepent === SizeDependet.EXPLICIT )
						ui_internal::valideHeight = rect.height;

				} else {
					// мое поведение осталось прежним (PARENT)
					dispatch = true;
					this.layoutTracer.layoutObjects();
				}

			} else {
				if (!layouted)
					this.layoutTracer.layoutObjects();
			}

			// Требуется вмешательство родителя
			if ( dispatch ) {

				var parentDecor:DisplayObjectContainerDecorator = this.$parent;

				dispatch = parentDecor.aggregate is UIComponent;

				// Частный случай, если родитель не UIC
				if ( this._widthDepent == SizeDependet.PARENT ) {
					if ( !dispatch ) {
						// Родитель не UIC, уже можно определить свою ширину
						ui_internal::valideWidth = parentDecor.width * this._percentWidth / 100;
						if (parentDecor.aggregate is Stage)
							this.assertStageListening();
					}
				}

				if ( this._heightDepent == SizeDependet.PARENT ) {
					if ( !dispatch ) {
						// Родитель не UIC, можно определить свою высоту.
						ui_internal::valideHeight = parentDecor.height * this._percentHeight / 100;
						if (parentDecor.aggregate is Stage)
							this.assertStageListening();
					}
				}
			}
		}

		// Значит все разрулилось в пределах этого контейнера. 
		if ( !dispatch )
			event.stopImmediatePropagation();

	}


	/**
	 * @private
	 */
	private function handler_stageResized(event:Event):void {
		if (event.eventPhase === EventPhase.AT_TARGET) {
			_stageTimer.reset();
			_stageTimer.start();
		}
	}

	/**
	 * @private
	 */
	private function handler_stageTimerComplete(event:TimerEvent):void {
		this._percentWidthChanged = !isNaN(this._percentWidth);
		this._percentHeightChanged = !isNaN(this._percentHeight);

		if (this._percentHeightChanged || this._percentWidthChanged)
			this.invalidate();
	}

	/**
	 * @private
	 */
	private function handler_boundingBoxKey(event:KeyboardEvent):void {
		this._showBounding = !this._showBounding;
		this._showBoundingChanged = true;
		super.invalidate();
	}
	
	private var _addedToStage:Boolean = false;

	/**
	 * @private
	 */
	private function handler_addedToStage(event:Event):void {
		// Часть механизма 
		if (this._addedToStage) {
			event.stopImmediatePropagation(); return;
		} else {
			this._addedToStage = true;
		}

		// Причина последующей валидации - добавление в список отображения.
		this._added = true;


		if ( !this._initialized )
			this.initialize();

		// При добавлении в список отображения необходимо уточнить стили компонента.
		// см. http://www.w3.org/TR/CSS21/selector.html#descendant-selectors
		this.rebuildStyleTable();

		keyboardManager.addKeyboardEventListener(KeyboardEvent.KEY_DOWN, this.handler_boundingBoxKey, Keyboard.SPACE, 0, true);
		
		this.invalidate();
	}

	private var _ancestorChain:Array = new Array();

	internal function get ancestorChain():Array {
		return this._ancestorChain.slice();
	}

	private function rebuildStyleTable():void {
		var parent:DisplayObjectContainer = this;

		this._ancestorChain = new Array();
		this._ancestorChain.push( getQualifiedClassName(this) );

		while (parent) {
			parent = parent.parent;

			if ( !(parent is UIComponent) )
				continue;

			var ancestorChain:Array = (parent as UIComponent).ancestorChain;
			this._ancestorChain.unshift.apply(null, ancestorChain);
			break;
		}

		if (this._ancestorChain.length > 1)
			this.styleManager.getStyleDescendantSelectors(this._ancestorChain);
	}

	/**
	 * @private
	 */
	private function handler_removedFromStage(event:Event):void {	
		this._addedToStage = false;

		const parent:	DisplayObject = super.parent;

		if ( parent is Stage ) {
			parent.removeEventListener(Event.RESIZE, this.handler_stageResized);
			this._stageListening = false;
		}

		this._parent = null;
	}


	/**
	 * @private
	 */
	private function handler_styleSheetChanged(event:Event):void {
		//trace('handler_styleSheetChanged');
		this._styleSheetChanged = true;
		this.invalidate();
	}


	//-------------------------------------------------------------------------
	//
	//   Event handlers: workaround bug
	//
	//-------------------------------------------------------------------------
}
}

//=============================================================================
//
//   Inner definitions
//
//=============================================================================

import flash.display.DisplayObject;
import flash.display.DisplayObjectContainer;
import flash.display.Sprite;
import flash.display.Stage;
import flash.events.Event;
import flash.geom.Rectangle;

import ru.goodcore.core.ILayoutTracer;
import ru.goodcore.display.ui.core.UIComponent;
import ru.goodcore.display.ui.core.ui_internal;
import ru.goodcore.utils.ObjectInfo;

/**
 * Адаптер для Stage
 * Возвращает для него ширину stageWidth и высоту stageHeight по запросу width и height
 */
internal class DisplayObjectContainerDecorator extends Sprite {

	public function DisplayObjectContainerDecorator(parent:DisplayObjectContainer):void {
		super();
		this._parent = parent;
	}

	private var _parent: DisplayObjectContainer;

	public function get aggregate():DisplayObject {
		return this._parent;
	}

	public override function get width():Number {
		if (this._parent is Stage)
			return (this._parent as Stage).stageWidth;

		return this._parent.width;
	}

	public override function get height():Number {
		if (this._parent is Stage)
			return (this._parent as Stage).stageHeight;

		return this._parent.height;
	}

	public override function get parent():DisplayObjectContainer {
		return this._parent;
	}

}


//-----------------------------------------------------------------------------
//
//  Helper class
//
//-----------------------------------------------------------------------------
/**
 * Контейнер для внутренних служебных детей, таких как информационное
 * текстовое поле с размерами компонента. Перехватывает события ADDED, REMOVED
 * 
 */
internal class InternalContainer extends Sprite {

	public function InternalContainer():void {
		super();
		super.focusRect = null;
		super.tabChildren = false;
		super.tabEnabled = false;
		super.mouseEnabled = false;
		super.mouseChildren = false;
		super.addEventListener(Event.ADDED,		this.handler_monitor, false, int.MAX_VALUE);
		super.addEventListener(Event.REMOVED,	this.handler_monitor, false, int.MAX_VALUE);
	}

	private function handler_monitor(event:Event):void {
		event.stopImmediatePropagation();
	}

}


//-----------------------------------------------------------------------------
//
//  Helper class
//
//-----------------------------------------------------------------------------

/**
 * Вспомогательный класс лайаутинга по умолчанию. Раскладкой не занимается, но 
 * вычисляет bounding box, составляющую детьми.
 */
internal class DefaultTracer implements ILayoutTracer {

	//-------------------------------------------------------------------------
	//
	//   Constructor
	//
	//-------------------------------------------------------------------------

	public function DefaultTracer() {
		super();
	}

	//-------------------------------------------------------------------------
	//
	//   Private const & variables
	//
	//-------------------------------------------------------------------------

	/**
	 * @private 
	 */
	private const _bounds:	Rectangle = new Rectangle();

	//-------------------------------------------------------------------------
	//
	//   Properties
	//
	//-------------------------------------------------------------------------

	public function get objectInfo():ObjectInfo {
		return null;
	}

	//---------------------------------
	//  target
	//---------------------------------
	/**
	 * @private
	 */
	private var _target:	DisplayObjectContainer;

	public function set target(container:DisplayObjectContainer):void {
		if ( this._target == container )
			return;
		this._target = container;
	}

	public function get target():DisplayObjectContainer {
		return this._target;
	}

	//-------------------------------------------------------------------------
	//
	//   Public methods
	//
	//-------------------------------------------------------------------------
	/**
	 * @inheritDoc 
	 */
	public function layoutObjects():Rectangle {
		if ( !this._target )
			return new Rectangle();

		var child:			DisplayObject;
		var uiChild:		UIComponent;
		const uiContainer:	UIComponent = this._target as UIComponent;

		if ( uiContainer ) {
			const list:				Vector.<DisplayObject> = uiContainer.getChildren();
			var len:				uint = list.length;

			if (!len)
				return new Rectangle(0, 0, this._target.width, this._target.height);

			const percentChilds:	Vector.<UIComponent> = new Vector.<UIComponent>();

			// Для начала определим всех детей, имеющих точные размеры.
			for (var i:uint = 0; i < len; i++) {

				uiChild = list[i]as UIComponent;

				if ( uiChild ) {

					if ( !uiChild.includeInLayout )
						continue;

					if ( 
						   !isNaN(uiChild.percentWidth)
						|| !isNaN(uiChild.percentHeight)
					) {
						percentChilds.push(uiChild);
					}

				}

			}

			len = percentChilds.length;

			if ( len ) {

				const ratioWidth:	Number = this._target.width / 100;
				const ratioHeight:	Number = this._target.height / 100;

				for (i = 0; i < len; i++) {
					uiChild = percentChilds[i];

					if ( !isNaN(uiChild.percentWidth) ) {
						uiChild.ui_internal::valideWidth = Math.floor( uiChild.percentWidth * ratioWidth );
					}

					if ( !isNaN(uiChild.percentHeight) ) {
						uiChild.ui_internal::valideHeight = Math.floor( uiChild.percentHeight * ratioHeight );
					}

				}
			}

		} else {

			throw new Error('Feature in progress');

		}

		return new Rectangle(0, 0, this._target.width, this._target.height);
	}

	/**
	 * @inheritDoc 
	 */
	public function measure():Rectangle {
		if ( !this._target )
			return new Rectangle();

		var bounds:Rectangle = this._target.getBounds(this._target);
		
		return bounds;

		return new Rectangle(0, 0, this._target.width, this._target.height);
	}

}